#include "QActive.hpp"
#include "QAssert.hpp"
#include "QFPkg.hpp"
#include "QFPort.hpp"

namespace QP {

Q_DEFINE_THIS_MODULE("QActive")

QActive::QActive(QStateHandler const initial, bool canCallJava)
    : QHsm(initial), m_prio(0), m_canCallJava(canCallJava) {}

void QActive::postFIFO(const std::shared_ptr<const QEvt>& e) {
  QF_CRIT_STAT_
  QF_CRIT_ENTRY_();
  if (m_eQueue.m_frontEvt == nullptr) {
    m_eQueue.m_frontEvt = e;       // deliver event directly
    QACTIVE_EQUEUE_SIGNAL_(this);  // signal the event queue
  } else {
    Q_ASSERT(m_eQueue.m_nFree != static_cast<QEQueueCtr>(0));
    QF_PTR_AT_(m_eQueue.m_ring, m_eQueue.m_head) = e;
    if (m_eQueue.m_head == static_cast<QEQueueCtr>(0)) {  // need to wrap?
      m_eQueue.m_head = m_eQueue.m_end;                   // wrap around
    }
    --m_eQueue.m_head;

    --m_eQueue.m_nFree;  // update number of free events
    if (m_eQueue.m_nMin > m_eQueue.m_nFree) {
      m_eQueue.m_nMin = m_eQueue.m_nFree;  // update minimum so far
    }
  }

  QF_CRIT_EXIT_();
}

void QActive::postLIFO(const std::shared_ptr<const QEvt>& e) {
  QF_CRIT_STAT_
  QF_CRIT_ENTRY_();
  if (m_eQueue.m_frontEvt == nullptr) {  // is the queue empty?
    m_eQueue.m_frontEvt = e;             // deliver event directly
    QACTIVE_EQUEUE_SIGNAL_(this);        // signal the event queue
  } else {
    Q_ASSERT(m_eQueue.m_nFree != static_cast<QEQueueCtr>(0));

    ++m_eQueue.m_tail;
    if (m_eQueue.m_tail == m_eQueue.m_end) {         // need to wrap the tail?
      m_eQueue.m_tail = static_cast<QEQueueCtr>(0);  // wrap around
    }

    QF_PTR_AT_(m_eQueue.m_ring, m_eQueue.m_tail) = m_eQueue.m_frontEvt;
    m_eQueue.m_frontEvt = e;  // put event to front

    --m_eQueue.m_nFree;  // update number of free events
    if (m_eQueue.m_nMin > m_eQueue.m_nFree) {
      m_eQueue.m_nMin = m_eQueue.m_nFree;  // update minimum so far
    }
  }
  QF_CRIT_EXIT_();
}

std::shared_ptr<const QEvt> QActive::get_() {
  QF_CRIT_STAT_
  QF_CRIT_ENTRY_();

  QACTIVE_EQUEUE_WAIT_(this);  // wait for event to arrive directly
  std::shared_ptr<const QEvt> e = m_eQueue.m_frontEvt;
  if (m_eQueue.m_nFree != m_eQueue.m_end) { //any events in the ring buffer?
    // remove event from the tail
    m_eQueue.m_frontEvt = QF_PTR_AT_(m_eQueue.m_ring, m_eQueue.m_tail);
    if (m_eQueue.m_tail == static_cast<QEQueueCtr>(0)) {  // need to wrap?
      m_eQueue.m_tail = m_eQueue.m_end;                   // wrap around
    }
    --m_eQueue.m_tail;

    ++m_eQueue.m_nFree;          // one more free event in the ring buffer
  } else {
    m_eQueue.m_frontEvt = nullptr;             // the queue becomes empty
    QACTIVE_EQUEUE_ONEMPTY_(this);
  }
  QF_CRIT_EXIT_();
  return e;
}

void QActive::unsubscribeAll(void) const {
  uint8_t p = m_prio;
  Q_REQUIRE_ID(500, (static_cast<uint8_t>(0) < p) &&
                        (p <= static_cast<uint8_t>(QF_MAX_ACTIVE)) &&
                        (QF::active_[p] == this));
  for (int sig = Q_USER_SIG; sig < QF_maxSignal_; ++sig) {
    QF_CRIT_STAT_
    QF_CRIT_ENTRY_();
    if (QF_PTR_AT_(QF_subscrList_, sig).hasElement(p)) {
      QF_PTR_AT_(QF_subscrList_, sig).remove(p);
    }
    QF_CRIT_EXIT_();
  }
}

void QActive::subscribe(int sig) const {
  uint8_t p = m_prio;
  Q_REQUIRE_ID(300, (Q_USER_SIG <= sig) && (sig < QF_maxSignal_) &&
                        (static_cast<uint8_t>(0) < p) &&
                        (p <= static_cast<uint8_t>(QF_MAX_ACTIVE)) &&
                        (QF::active_[p] == this));
  QF_CRIT_STAT_
  QF_CRIT_ENTRY_();
  QF_PTR_AT_(QF_subscrList_, sig).insert(p);  // insert into subscriber-list
  QF_CRIT_EXIT_();
}

void QActive::unsubscribe(int sig) const {
  uint8_t p = m_prio;
  Q_REQUIRE_ID(400, (Q_USER_SIG <= sig) && (sig < QF_maxSignal_) &&
                        (static_cast<uint8_t>(0) < p) &&
                        (p <= static_cast<uint8_t>(QF_MAX_ACTIVE)) &&
                        (QF::active_[p] == this));
  QF_CRIT_STAT_
  QF_CRIT_ENTRY_();
  QF_PTR_AT_(QF_subscrList_, sig).remove(p);  // remove from subscriber-list
  QF_CRIT_EXIT_();
}

void QActive::defer(QEQueue* const eq,
                    const std::shared_ptr<const QEvt>& e) const {
  eq->postFIFO(e);
}

bool QActive::recall(QEQueue* const eq) {
  auto e = eq->get();                    // try to get evt from deferred queue
  bool const recalled = (e != nullptr);  // event available?
  if (recalled) {
    postLIFO(e);  // post it to the front of the Active Object's queue

    QF_CRIT_STAT_
    QF_CRIT_ENTRY_();
    QF_CRIT_EXIT_();
  }
  return recalled;
}
}